from core.base import ComponentBase, NewWidgetType, NewActionType, Expression, \
                      AttributeBase, ReferenceLink, ComponentSelected, CompoundData, NewComponentInst, \
                      ObjectSelected, PySignal
from core import cmds
#from core.data import TreeHandler, TreeNode, DataHandleList, DataHandle

import os, functools, time, inspect, copy, types

from PyQt4.QtCore import SIGNAL
from PyQt4.QtGui import QCursor

from core.base import StaticData, Expression
from core import util


class Component(ComponentBase):
    ''' '''
    #name = String(default="noname")
    
    def __init__(self, **kw):
        ComponentBase.__init__(self, **kw)
    
    #def __str__(self): 
    #    return str(self.name) if hasattr(self, "name") else "<object: " + self.__class__.__name__ + ">"# + " (" + self.__class__.__name__ + ")"# " + str(id(self))


    def Class(self): return self.__class__
    
    @classmethod
    def base(self): return self.__bases__[0]


class Attribute(AttributeBase):
    default = None
    
    def __init__(self, **kw):
        self.proxy = kw.get('proxy')
        self.meta_data = {}
        if not self.proxy:
            for name, data in self.static():
                setattr(self, name, kw.get(name, data.default))
        self.name = kw.get('name')
        self.default = kw.get('default')
        self.doc = kw.get('doc', '')
        self.depends = kw.get('depends', [])
        
        self.change = PySignal()
    
    def create_proxy(self, name):
        return self.__class__(name=name, proxy=self)
    
    def has_children(self, instance):
        return False
    
    def has_parent(self, instance):
        return hasattr(self, 'parent')
    
    def _set_static_data(self, name, data):
        if self.proxy is not None:
            setattr(self.proxy, name, data)
            return
        self.meta_data[name] = data
        
    def _get_static_data(self, name):
        if self.proxy is not None:
            return getattr(self.proxy, name)
        return self.meta_data[name]
    
    def default_value(self, instance):
        return self.__class__.default
    
    def get_expression(self, instance):
        exp = instance._get_data(self)
        if not isinstance(exp, Expression):
            return None
        return exp
    
    def is_expression(self, instance):
        return isinstance(instance._get_data(self), Expression)
    
    def is_output(self):
        return bool(self.depends)
        
    def static(self):
        for name, value in inspect.getmembers(self.__class__):
            if isinstance(value, StaticData):
                yield (name, value)
    
    def can_depend_on(self, attr):
        return AttributeBase.can_depend_on(self, attr)
    
    def stored_data(self, instance):
        return instance._get_data(self)
    
    def can_convert_to(self, datatype):
        try:
            if not isinstance(datatype, (types.ClassType, type)):
                datatype = datatype.__class__
            converter = getattr(self, 'from_' + datatype.__name__)
            return callable(converter)
        except AttributeError, e:
            return False
        
    def get_convert_func(self, datatype):
        return getattr(self, 'from_' + datatype.__name__)
    
    def convert(self, value):
        return self.get_convert_func(value.__class__)(value)
    
    def get(self, instance, value):
        if isinstance(value, Expression):
            return value.eval(instance)
        try:
            self.validate(value)
        except Exception, e:
            print 'Data Valdiation Failed:', str(e)
            try:
                value = self.convert(value)
            except Exception, ce:
                print 'Convertion failed:', str(ce)
                print 'Resetting to default value'
                value = self.default_value(instance)
            setattr(instance, self.name, value)
        return value
    
    def validate(self, value):
        return True
    
    def set(self, instance, value):
        return value
    
    def __getattr__(self, name):
        if self.proxy is not None:
            return getattr(self.proxy, name)
        raise AttributeError(name)
        #return object.__getattr__(self, name)
    
    def __set__(self, instance, value):
        #if not util.pickleable(value):
        #    raise TypeError("Value is not pickleable: " + str(value))
        
        if isinstance(value, Expression):
            instance._set_data(self, value)
            self.change(instance, value)
            return
        
        convert_fn = 'from_'+value.__class__.__name__
        if hasattr(self, convert_fn):
            data = getattr(instance, self.name)
            getattr(self, convert_fn)(data, value)
            self.change(instance, value)
            return
        
        if value is None or self.can_set(value):
            instance._set_data(self, self.set(instance, value))
            self.change(instance, value)
            return
        
        raise ValueError("Cannot set value: "+str(value)+" on Attribute: "+str(instance)+'.'+str(self))
    
    def __get__(self, instance, owner=None):
        if instance is None:
            return self
        curr_val = instance._get_data(self)
        return self.get(instance, curr_val)



class String(Attribute):
    default = ""
    mask = StaticData("")
    
    def can_set(self, value):
        if not isinstance(value, str):
            raise TypeError("value must be string")
        return True

    def from_int(self, value):
        return str(value)

    def validate(self, value):
        return self.can_set(value)



class Type(Attribute):
    
    def can_set(self, value):
        return issubclass(value, Component)

class Choices(Attribute):
    ''' '''
    choices = StaticData([])
    
    def set(self, instance, value):
        if value is None:
            return self.choices[0]
        if not value in self.choices:
            raise TypeError("No choice named: "+ str(value))
        return value
    
    
class Date(Attribute):
    ''' '''
    
    def can_set(self, value):
        return isinstance(value, float)
    
    def can_depend_on(self, attr):
        if type(attr) in [Int]:
            return True
        else:
            return super(self.__class__, self).can_depend_on(attr)
    
    def get(self, instance, value):
        #value = time.asctime(time.localtime(value))
        return value
        

class Reference(Attribute):
    ''' Attribute for defining a reference to an other component '''
    type = StaticData(Component)
    
    #def __init__(self, **kw):
    #    self.component_type = kw.get('type')
    #    Attribute.__init__(self, **kw)
    
    def can_set(self, value):
        print 'Reference', value, type(value).__bases__, self.type
        print type(value).__bases__[0] == self.type
        return issubclass(type(value), self.type)
    
    def set(self, instance, value):
        if isinstance(value, ReferenceLink):
            return value
        if isinstance(value, (Component,)):
            link = ReferenceLink(instance, value)
            return link
        return value
    
    def get(self, instance, value):
        print 'GET REFERENCE', value
        return value.reference


class PolyData(object):
    _attrs = []
    _data = {}
    _prototype = None
    _parent = None
    
    def __new__(cls, prototype, parent, parent_component):
        #print 'INIT POLY DATA'
        inst = object.__new__(cls)
        inst._attrs = []
        inst._data = {}
        inst._prototype = prototype
        inst._parent = parent
        inst._parent_component = parent_component
        inst.attr_added = PySignal()
        inst.attr_removed = PySignal()
        return inst
    #def init(self):
    #    self._attrs = []
    
    def __getstate__(self):
        return self._data
    
    def __setstate__(self, state):
        for name, value in state.items():
            self.add(value, name=name)
    
    def clear(self):
        self._attrs = []
        self._data = {}
    
    def attrs(self):
        #print self, dir(self)
        #print hasattr(self, '_attrs')
        return self._attrs
    
    def index_name(self, key):
        if isinstance(key, str):
            pass
    
    def children(self):
        pass
    
    def name_exists(self, name):
        attr = self.attr_by_name(name)
        return bool(attr)
    
    def uniq_name(self):
        name_prefix = "id_"
        start_id = 0
        while 1:
            name = name_prefix + str(start_id)
            if not self.name_exists(name):
                return name
            start_id += 1   
    
    def new_attr(self, name=None):
        if name is None:
            name = self.uniq_name()
        attr = self._prototype.create_proxy(name)
        
        attr.parent = self._parent
        self._attrs.append(attr)
        return attr
    
    def attr_by_name(self, name):
        for attr in self.attrs():
            if attr.name == name:
                return attr
    
    def attr_by_logical_index(self, id): pass
    
    def attr_by_physical_index(self, id):
        return self.attrs()[id]
    
    def get_attr(self, id):
        if isinstance(id, str):
            return self.attr_by_name(id)
        if isinstance(id, int):
            return self.attr_by_physical_index(id)
    
    def add(self, data=None, name=None):
        attr = self.new_attr(name)
        if data is None:
            data = attr.default_value(self._parent_component)
            print 'ADD DATA', data, self._parent_component
        self.set_item(attr, data)
        print 'ADDING ATTR', attr, attr.name
        self.attr_added(self, attr)
        self._parent_component.change(self._parent_component, attr)
        return attr
    
    def remove(self, attr):
        self._attrs.remove(attr)
        self.attr_removed(self, attr)
        self._parent_component.change(self._parent_component, attr)
        
    def __getitem__(self, key):
        return self.get_item(key)
    
    def __setitem__(self, key, value):
        self.set_item(key, value)
        
    def __setattr__(self, name, value):
        try:
            return self.set_item(name, value)
        except AttributeError, e:
            return object.__setattr__(self, name, value)
        
    
    def __getattr__(self, name):
        try:
            return self.get_item(name)
        except AttributeError, e:
            return object.__getattr__(self, name)
            
        
    def get_item(self, attr):
        if not isinstance(attr, Attribute):
            attr = self.get_attr(attr)
        return attr.__get__(self)
    
    def set_item(self, attr, value):
        if not isinstance(attr, Attribute):
            attr = self.get_attr(attr)
        attr.__set__(self, value)
        self._parent_component.change(self._parent_component, self._parent)


    def _set_data(self, attr, value):
        print 'setting data', attr, value
        self._data[attr.name] = value
    def _get_data(self, attr):
        return self._data.get(attr.name)

class PolyAttribute(Attribute):
    itemtype = StaticData(Attribute)
    
    def default_value(self, instance):
        return PolyData(self.itemtype, self, instance)
    
    def validate(self, value):
        if not value._parent is self:
            raise ValueError("Data does not match attribute")
        
    def has_children(self, instance):
        if self.is_expression(instance):
            return False
        return True
    
    def children(self, instance):
        
        poly_data = instance._get_data(self.name)
        return poly_data.attrs()

class List(PolyAttribute):
      
    def add(self, value):
        attr = self.new_attr()
        attr.name = self.index_name(len(self))
        self.add_dynamic_attr(attr, value)
        return attr
        #assert list(self.attrs())[-1] == attr
        
    def rem(self, index):
        self.del_dynamic_attr(self.attr_at(index))
        
    def index_name(self, index):
        return "id_" + str(index)
        
    def clear(self):
        for attr in list(self.attrs()):
            self.del_dynamic_attr(attr)
        assert len(self) == 0
        
    def set_index(self, value, index):
        length = len(self)
        if length > index:
            setattr(self, self.index_name(index), value)
        elif length == index:
            self.add(value)
        else:
            raise ValueError()
        
    def from_list(self, instance, L):
        instance.clear()
        for item in L:
            instance.add(item)
            
    def from_List(self, L):
        self.clear()
        for item in L:
            self.add(item)
    
    def get_in_connect(self, output):
        if isinstance(output, List):
            return self.name
        attr = self.add(None)
        return self.name + '.' + attr.name
    
    #def from_NoneType(self, n):
    #    self.clear()


class ListComponent(Component):
    input = List(itemtype=Attribute())
    output = List(itemtype=Attribute(), depends=(input,))

    def compute_output(self):
        self.output.clear()
        for item in self.input:
            self.output.add(item)
        return self.output

"""
class Tree(CompoundData):
    #item = Attribute()
    children = List(itemtype=CompoundData())
    item_type = StaticData(Attribute())
    
    def __init__(self, **kw):
        CompoundData.__init__(self, **kw)
        #print 'ITEM TYPE', kw.get('item_type'), self.name
        
        #self.item_type.name = "item"
        
        #self.add_dynamic_attr(self.item_type, "")
        
        itemattr = self.item_type.copy()
        itemattr.name = "item"
        self.add_dynamic_attr(itemattr, "'test'")
        
        #self.item_type.name = "item"
        #print 'ITEM ATTR', self.name, itemattr, list(self.attrs())
        
        #print "INIT ATTRS", self.name, list(self.attrs())
        #print 'ITEM TYPE', self.item_type
    
    def clear(self):
        self.children.clear()
    
    def get_item(self, data):
        ''' Get the Tree Item with item == data '''
        for depth, tree_item in self.walk():
            if tree_item is None or not isinstance(tree_item, Tree):
                continue
            if tree_item.item == data:
                return tree_item
    
    
    def add_child(self, data):
        item_attr = self.item_type.copy()
        item_attr.name = "item"
        item = Tree(item_type = item_attr)
        item.item = data
        #item.add_dynamic_attr(item_attr, data)
        self.children.add(item)
        return item
    
    def walk(self, depth=0):
        yield (depth, self.item)
        for child in self.children:
            for sub_depth, sub_child in child.walk(depth+1):
                yield (sub_depth, sub_child)
                
    def pprint(self, depth_string="  "):
        for depth, item in self.walk():
            print depth_string*depth + str(item)
            
    def from_NoneType(self, n):
        self.item = None
        
    def from_list(self, L):
        self._add_list(self, L)
        
    def _add_list(self, parent, L):
        if parent is None:
            return
        if not util.isiter(L):
            parent.add_child(L)
        else:
            last_parent = parent
            for item in L:
                if util.isiter(item):
                    self._add_list(last_parent, item)    
                else:
                    child = parent.add_child(item)
                    last_parent = child
"""

class Slot(Attribute):
    
    def __init__(self, **kw):
        self.method_name = kw.get('method')
        self._signals = {}
        Attribute.__init__(self, **kw)
    
    def can_set(self, value):
        return isinstance(value, str)
    
    def get(self, instance, value):
        #if isinstance(value, DataHandle):
        #    value = value._data
        
        return cmds.signal(value)
    
    def set(self, instance, value):
        if self.method_name is None:
            return value
        method = getattr(instance, self.method_name)
        if not callable(method): return value
        
        last_signal = cmds.signal(value)
        if last_signal:
            last_signal.disconnect(method)
        
        signal = cmds.signal(value)
        
        if not signal: return value
        
        signal.connect(method)
        return value

class Bool(Attribute):
    
    def can_set(self, value):
        return isinstance(value, bool)
    
    



class Text(String): pass
    

class Path(Attribute):
    def can_set(self, value):
        return isinstance(value, str)# and os.path.exists(value)

class Int(Attribute):
    min = StaticData(None)
    max = StaticData(None)
    
    def default_value(self, instance):
        return 0 if self.min is None else self.min
    
    def can_set(self, value):
        if not isinstance(value, (int,long,float,)):
            raise TypeError("Int must be int or long")
        if self.min is not None and value < self.min:
            raise ValueError("Value must be greater then " + str(self.min))
        if self.max is not None and value > self.max:
            raise ValueError("Value must be less then " + str(self.max))
        return True
    
    def validate(self, value):
        return self.can_set(value)
    
    def from_str(self, value):
        return int(value)
    
    def from_Date(self, value):
        return 0
    
    
class File(Attribute):
    
    def __init__(self, **kw):
        self.must_exist = kw.get('must_exist', False)
        Attribute.__init__(self, **kw)
    
    def can_set(self, value):
        return isinstance(value, str) and (not self.must_exist or os.path.exists(value))
    
class Image(File):
    types = ["jpg", "jpeg", "png", "gif"]
    
    def can_set(self, value):
        return File.can_set(self, value) and os.path.splitext(value)[1][-1:].lower() in self.types
    
    def get(self, instance, value):
        return value
    
    
class Function(Attribute):
    
    args = StaticData([])
    
    def __init__(self, **kw):
        #self.args = kw.get('args',[])
        
        #self.fn = self._construct_fn(self.args)
        Attribute.__init__(self, **kw)
        
    def __load__(self, data):
        if callable(data): pass
      
    def get(self, instance, value):
        return self._fn_from_string(instance, value)
        
    def can_set(self, value):
        return isinstance(value, str)
        
    def _fn_from_string(self, instance, string):
        if string is None: return
        #if not self.args:
        #    return lambda: eval(string)
        cmds.reload()
        arg_string = ','.join(self.args)
        if arg_string: arg_string += ','
        fn_code = "def fn(" + arg_string + " self=None): return " + string
        container = {}
        exec fn_code in cmds.__dict__, container
        fn = container['fn']
        return functools.partial(fn, self=instance)
        
        
class Exp(Attribute):
    
    def __init__(self, **kw):
        self.auto_eval = kw.get('auto_eval', True)
        Attribute.__init__(self, **kw)
    
    def expression_string(self, data_handle):
        return data_handle._data
    
    def get(self, instance, value):
        if not self.auto_eval:
            return value
        try:
            return value.eval()
        except Exception, e:
            print('Expression ' + str(value) + ' failed: ' + str(e))
            return None
    
    def can_set(self, value):
        return isinstance(value, str)
    
    def set(self, instance, value):
        return Expression(value)





class ActionMeta(type):
    def __new__(self, name, bases, ns):
        cls = type.__new__(self, name, bases, ns)
        NewActionType(cls)
        return cls

class Action(object):
    __metaclass__ = ActionMeta
    types = None
    options = []
    
    def get_name(self, obj):
        if self.name is None: return self.__class__.__name__
        if callable(self.name): return self.name(obj)
        return str(self.name)
    
    def do(self, *objects):
        pass
    
    
class View(Component):
    ''' '''
    widget = None
    update_on = List(itemtype=Slot(method="update_widgets"))
    
    def init(self, *args, **kw):
        self.widgets = []
        self.editors = {}
        self.change.connect(self.on_data_change)
        ComponentSelected.connect(self.current_changed)
        ObjectSelected.connect(self.current_changed)
    
    def update_widget(self, widget): return
    
    def current_changed(self, component): return
    
    
    def recreate_widgets(self):
        old_widgets = self.widgets[:]
        self.widgets = []
        for widget in old_widgets:
            parent = widget.parent()
            new_widget = self.new_widget()
            new_widget.setParent(parent)
            widget.deleteLater()
    
    def update_widgets(self,*args,**kw):
        for widget in self.widgets:
            self.update_widget(widget)
    
    def on_data_change(self, attr, value):
        self.update_widgets()
    
    def on_widget_deleted(self, widget):
        self.widgets.remove(widget)
    
    def on_context_menu(self, point, widget):
        print 'CONTEXT MENU'
        obj = widget.model().itemFromIndex(widget.currentIndex()).data_obj
        menu = cmds.action_menu(obj, widget)
        if menu is None: return
        menu.popup(QCursor.pos())
    
    def setup_widget(self, widget):
        pass
    
    def rem_widget(self, widget):
        pass
        #widget.setParent(None)
        #widget.deleteLater()
    
    def new_widget(self):
        widget = self._init_widget()
        self.widgets.append(widget)
        widget.connect(widget, SIGNAL("destroyed()"), lambda w=widget: self.on_widget_deleted(w))
        widget.connect(widget, SIGNAL("customContextMenuRequested(const QPoint&)"), lambda p, widget=widget: self.on_context_menu(p,widget))
        self.setup_widget(widget)
        self.update_widget(widget)
        return widget
    
    def _init_widget(self, cls=None):
        if cls:
            widget = cls.widget
        else:
            widget = self.__class__.widget
        widget_inst = widget(None)
        return widget_inst
    

    
class Node(Component):
    pass